昨天我写到“所有Javascript函数都是闭包”,有些同学表示还是接受不能。我好好的一个函数,怎么就成闭包了?那么,让我们来探究一下,Chrome(V8)到底是怎样实现闭包的。
从闭包到[[Scopes]]
现在按下F12,打开console,让我们随便找一个实验对象:
function simpleFunc() { }
// <- undefined
超简单超正常的函数吧,我们来验证一下:
simpleFunc
// <- ƒ simpleFunc() { }
说了超正常的,哪里闭包了?现在试试这个:
console.dir(simpleFunc)
// ƒ simpleFunc()
// arguments: null
// caller: null
// length: 0
// name: "simpleFunc"
// prototype: {constructor: ƒ}
// __proto__: ƒ ()
// [[FunctionLocation]]: VM000:1
// [[Scopes]]: Scopes[1]
咦,[[Scopes]]
是什么?打开一看:
// [[Scopes]]: Scopes[1]
// 0: Global {type: "global", name: "", object: Window}
这就是闭包的实现。东西都存在这里了。看起来simpleFunc
只不过是纯洁的函数,但它实际上是(空的)自身代码+全局变量环境。换句话说,它正是“函数和声明该函数的词法环境的组合”。
再来个稍微复杂点的例子:
{
let localVar = 1;
function dirtyFunc() { return localVar++ }
}
// <- ƒ dirtyFunc() { return localVar++ }
console.dir(dirtyFunc)
// ƒ dirtyFunc()
// [[Scopes]]: Scopes[2]
// 0: Block
// localVar: 1
// 1: Global {type: "global", name: "", object: Window}
看,localVar
存在这里了吧!大家老说什么“保持运行的数据状态”云云,其实都在[[Scopes]]
里。dirtyFunc
看起来是个普通的函数,但[[Scopes]]
里却混了些东西。
所以,如果我们说人话,闭包实际上就是——
函数的代码+[[Scopes]]
超级好理解了吧。
宁愿用this
也不用闭包
接下来让我们对闭包做些更深入的解析,然后就知道为什么大家宁愿用this
也不用闭包了。
[[Scopes]]
能用代码访问/复制/修改吗?
不能。想不靠console,找到副作用在哪儿?不行。想深拷贝目前状态?不行。想历史回放?不行。debug?自己慢慢琢磨去吧!
闭包会把所有东西都存下来吗?
{
let localVar = 1;
let unusedVar = 2;
function dirtyFunc2() { return localVar++ }
}
console.dir(dirtyFunc2)
// ƒ dirtyFunc()
// [[Scopes]]: Scopes[2]
// 0: Block
// localVar: 1
// 1: Global {type: "global", name: "", object: Window}
至少Chrome是不会把所有东西都塞到闭包里的。
那闭包对垃圾回收没害处?
{
let localVar = new Uint8Array(1000000000)
function dirtyFunc3() { return localVar }
function cleanFunc() { }
}
var dirtyFunc3 = null
console.dir(cleanFunc)
// ƒ cleanFunc()
// [[Scopes]]: Scopes[2]
// 0: Block
// localVar: Uint8Array(1000000000) [0, 0, …]
// 1: Global {type: "global", name: "", object: Window}
dirtyFunc3
和cleanFunc
共享同一个[[Scopes]]
项,但这个[[Scopes]]
项并不会因为dirtyFunc3
被回收而动态更新!所以无辜的cleanFunc
就只好一直带着这1GB的垃圾,内存泄漏妥妥的。作为强迫症,这是我讨厌闭包最重要的原因。
真的所有Javascript函数都是闭包吗?
console.dir(alert)
// ƒ dirtyFunc()
// [[Scopes]]: Scopes[0]
// No properties
抱歉,我可能是不太严谨。很明显,浏览器自带的原生API函数都是在【里世界】声明的,所以没有词法环境,自然[[Scopes]]
是空的。它们不是闭包。
最佳实践
宁愿用this
也不用闭包。原因详见我的上一篇文章(从过程式到函数式)。
我的相关文章
以上所有代码按Mozilla Public License, v. 2.0授权。
以上所有文字内容按CC BY-NC-ND 4.0授权。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。